-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LibWeb+LibCrypto: Add Ed448 support in WebCryptoAPI #3009
base: master
Are you sure you want to change the base?
Conversation
Libraries/LibCrypto/Curves/Ed448.cpp
Outdated
ErrorOr<ByteBuffer> Ed448::generate_private_key() | ||
{ | ||
auto* key = OPENSSL_TRY_PTR(EVP_PKEY_Q_keygen(nullptr, nullptr, "ED448")); | ||
ArmedScopeGuard const free_key { [&] { EVP_PKEY_free(key); } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you never disarm the scope guard, you can use ScopeGuard instead.
Libraries/LibCrypto/Curves/Ed448.h
Outdated
size_t key_size() { return 57; } | ||
size_t signature_size() { return 114; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these member functions should at least be const.
size_t key_size() const { return 57; }
if not constexpr
as well
constexpr size_t key_size() const { return 57; }
Libraries/LibCrypto/Curves/Ed448.cpp
Outdated
ErrorOr<bool> Ed448::verify(ReadonlyBytes public_key, ReadonlyBytes signature, ReadonlyBytes message, ReadonlyBytes context) | ||
{ | ||
auto* key = OPENSSL_TRY_PTR(EVP_PKEY_new_raw_public_key_ex(nullptr, "ED448", nullptr, public_key.data(), public_key.size())); | ||
ArmedScopeGuard const free_key { [&] { EVP_PKEY_free(key); } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of using all these scope guards, perhaps we should invent some OpenSSL wrappers?
class OpenSSLPrivateKey {
public:
ErrorOr<OpenSSLPrivateKey> create(StringView name, ReadonlyBytes public_key);
~OpenSSLPrivateKey();
EVP_PKEY* ptr() { return m_ptr; }
private:
OpenSSLPrivateKey(EVP_PKEY* ptr) : m_ptr(ptr) {};
EVP_PKEY* m_ptr { nullptr };
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, at least for the most common types, I'll protorype something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 to me, a one-off scope guard is a handy tool, but using several identical scope guards in every method of a class screams "add some RAII"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really not sure about TRY(OpenSSL_PKEY::create(EVP_PKEY_Q_keygen, nullptr, nullptr, "ED448"));
, does it make any sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that was dumb, should make more sense now
After this goes in, a comment on WICG/webcrypto-secure-curves#20 seems appropriate |
1a3dde2
to
fc1b5c5
Compare
|
||
namespace Crypto::Curves { | ||
|
||
ErrorOr<ByteBuffer> Ed448::generate_private_key() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-maintainer take: I think we should avoid fallible returns for things that most likely won't fail. Only because if they do fail, it's either us using the API incorrectly or something completely out of our control like a memory allocation failure. Failing fast in those scenarios seems better than bubbling it up to callers.
Slight bonus is that call sites of these functions become a little less noisy. Anyway, this could just return ByteBuffer
and MUST(...)
any fallible calls it makes IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I am of the opposite idea: since error handling comes at a little cost we should try to cover every case to preserve uniformity and future proofing.
Libraries/LibCrypto/OpenSSL.h
Outdated
auto* err_message = ERR_error_string(err, nullptr); \ | ||
return Error::from_string_view(StringView { err_message, strlen(err_message) }); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is safe to do, IIRC ERR_error_string
will overwrite the static buffer on every call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, I should make a new buffer beforehand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, not sure if there's a better way to make the buffer and pass it around.
Libraries/LibCrypto/OpenSSL.h
Outdated
VERIFY(err); \ | ||
auto err_message = TRY(ByteBuffer::create_zeroed(256)); \ | ||
ERR_error_string_n(err, reinterpret_cast<char*>(err_message.data()), 256); \ | ||
return Error::from_string_view(StringView { err_message.data(), strlen(reinterpret_cast<char*>(err_message.data())) }); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you've written is always a use after free. AK::Error cannot own a string. If you want an owned string, you need to use a custom error type in ErrorOr<T, E>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, I guess we would need something like OpenSSLError
which would then escape up to whoever is making the call inhibiting usage of TRY
along the way unless ErrorOr<U, OpenSSLError>
is used.
I woundn't want to have
ErrorOr<ByteBuffer, OpenSSLError> Ed448::sign(ReadonlyBytes private_key, ReadonlyBytes message, ReadonlyBytes context)
since OpenSSL should be an internal detail. Do you reckon any other way to handle this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just suggest an enum or custom error strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have opted for the simplest solution right now: create an error via string literal and log the error. Another option could be to provide an error literal with the macro.
Add a couple of macros to aid error handling with OpenSSL and some RAII classes that manage the lifetime of some OpenSSL objects.
Implement the Ed448 curve for signing and verifying using OpenSSL. The methods could be all made static, but all other curves are not. I think this is material for further refactoring.
Add full support for Ed448 and import relevant tests.
Make use of the recently merged OpenSSL PR to implement Ed488 quick and easy.
Apart from implementing Ed448, I've also started defining some macros to aid development of code based on OpenSSL (see
OPENSSL_TRY
andOPENSSL_TRY_PTR
).The implemention is a bit different from the other curves obviously and now all the methods could be static. I have evaluated some possibilities:
OwnPtr
s everywhereRSA
and require to initialize it with the keys instead of passing them to the methodsI think the decision can wait based on how the implementation of other things using OpenSSL goes, but wanted to share.